Passed
Push — master ( b7cf3e...4763f6 )
by Rafael S.
03:31
created

wav-buffer-reader.js ➔ readFmtExtension_   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
c 1
b 0
f 0
nc 4
nop 4
dl 0
loc 16
rs 9.8
1
/*
2
 * Copyright (c) 2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview Get structured wav data out of buffers.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import {riffChunks, findChunk_} from '../vendor/riff-chunks.js';
31
import BufferIO from './bufferio.js';
32
33
let io = new BufferIO();
34
35
/**
36
 * Set up the WaveFile object from a byte buffer.
37
 * @param {!Uint8Array} buffer The buffer.
38
 * @param {boolean} samples True if the samples should be loaded.
39
 * @param {!Object} wav True if the samples should be loaded.
40
 * @throws {Error} If container is not RIFF, RIFX or RF64.
41
 * @throws {Error} If no 'fmt ' chunk is found.
42
 * @throws {Error} If no 'data' chunk is found.
43
 */
44
export default function readWavBuffer(buffer, samples, wav) {
45
  io.head_ = 0;
46
  let uInt32_ = {bits: 32, be: false};
47
  let uInt16_ = {bits: 16, be: false};
48
  readRIFFChunk_(buffer, wav, uInt32_, uInt16_);
49
  /** @type {!Object} */
50
  let chunk = riffChunks(buffer);
51
  readDs64Chunk_(buffer, chunk.subChunks, wav, uInt32_);
52
  readFmtChunk_(buffer, chunk.subChunks, wav, uInt32_, uInt16_);
53
  readFactChunk_(buffer, chunk.subChunks, wav, uInt32_);
54
  readBextChunk_(buffer, chunk.subChunks, wav, uInt32_, uInt16_);
55
  readCueChunk_(buffer, chunk.subChunks, wav, uInt32_);
56
  readSmplChunk_(buffer, chunk.subChunks, wav, uInt32_);
57
  readDataChunk_(buffer, chunk.subChunks, samples, wav);
58
  readJunkChunk_(buffer, chunk.subChunks, wav);
59
  readLISTChunk_(buffer, chunk.subChunks, wav, uInt32_, uInt16_);
60
  bitDepthFromFmt_(wav);
61
}
62
63
/**
64
 * Set the string code of the bit depth based on the 'fmt ' chunk.
65
 * @private
66
 */
67
function bitDepthFromFmt_(wav) {
68
  if (wav.fmt.audioFormat === 3 && wav.fmt.bitsPerSample === 32) {
69
    wav.bitDepth = '32f';
70
  } else if (wav.fmt.audioFormat === 6) {
71
    wav.bitDepth = '8a';
72
  } else if (wav.fmt.audioFormat === 7) {
73
    wav.bitDepth = '8m';
74
  } else {
75
    wav.bitDepth = wav.fmt.bitsPerSample.toString();
76
  }
77
}
78
79
/**
80
 * Read the RIFF chunk a wave file.
81
 * @param {!Uint8Array} bytes A wav buffer.
82
 * @throws {Error} If no 'RIFF' chunk is found.
83
 * @private
84
 */
85
function readRIFFChunk_(bytes, wav, uInt32_, uInt16_) {
86
  io.head_ = 0;
87
  wav.container = io.readString_(bytes, 4);
88
  if (['RIFF', 'RIFX', 'RF64'].indexOf(wav.container) === -1) {
89
    throw Error('Not a supported format.');
90
  }
91
  uInt16_.be = wav.container === 'RIFX';
92
  uInt32_.be = uInt16_.be;
93
  wav.chunkSize = io.read_(bytes, uInt32_);
94
  wav.format = io.readString_(bytes, 4);
95
  if (wav.format != 'WAVE') {
96
    throw Error('Could not find the "WAVE" format identifier');
97
  }
98
}
99
100
/**
101
 * Read the 'fmt ' chunk of a wave file.
102
 * @param {!Uint8Array} buffer The wav file buffer.
103
 * @param {!Object} signature The file signature.
104
 * @throws {Error} If no 'fmt ' chunk is found.
105
 * @private
106
 */
107
function readFmtChunk_(buffer, signature, wav, uInt32_, uInt16_) {
108
  /** @type {?Object} */
109
  let chunk = findChunk_(signature, 'fmt ');
110
  if (chunk) {
111
    io.head_ = chunk.chunkData.start;
112
    wav.fmt.chunkId = chunk.chunkId;
113
    wav.fmt.chunkSize = chunk.chunkSize;
114
    wav.fmt.audioFormat = io.read_(buffer, uInt16_);
115
    wav.fmt.numChannels = io.read_(buffer, uInt16_);
116
    wav.fmt.sampleRate = io.read_(buffer, uInt32_);
117
    wav.fmt.byteRate = io.read_(buffer, uInt32_);
118
    wav.fmt.blockAlign = io.read_(buffer, uInt16_);
119
    wav.fmt.bitsPerSample = io.read_(buffer, uInt16_);
120
    readFmtExtension_(buffer, wav, uInt32_, uInt16_);
121
  } else {
122
    throw Error('Could not find the "fmt " chunk');
123
  }
124
}
125
126
/**
127
 * Read the 'fmt ' chunk extension.
128
 * @param {!Uint8Array} buffer The wav file buffer.
129
 * @private
130
 */
131
function readFmtExtension_(buffer, wav, uInt32_, uInt16_) {
132
  if (wav.fmt.chunkSize > 16) {
133
    wav.fmt.cbSize = io.read_(buffer, uInt16_);
134
    if (wav.fmt.chunkSize > 18) {
135
      wav.fmt.validBitsPerSample = io.read_(buffer, uInt16_);
136
      if (wav.fmt.chunkSize > 20) {
137
        wav.fmt.dwChannelMask = io.read_(buffer, uInt32_);
138
        wav.fmt.subformat = [
139
          io.read_(buffer, uInt32_),
140
          io.read_(buffer, uInt32_),
141
          io.read_(buffer, uInt32_),
142
          io.read_(buffer, uInt32_)];
143
      }
144
    }
145
  }
146
}
147
148
/**
149
 * Read the 'fact' chunk of a wav file.
150
 * @param {!Uint8Array} buffer The wav file buffer.
151
 * @param {!Object} signature The file signature.
152
 * @private
153
 */
154
function readFactChunk_(buffer, signature, wav, uInt32_) {
155
  /** @type {?Object} */
156
  let chunk = findChunk_(signature, 'fact');
157
  if (chunk) {
158
    io.head_ = chunk.chunkData.start;
159
    wav.fact.chunkId = chunk.chunkId;
160
    wav.fact.chunkSize = chunk.chunkSize;
161
    wav.fact.dwSampleLength = io.read_(buffer, uInt32_);
162
  }
163
}
164
165
/**
166
 * Read the 'cue ' chunk of a wave file.
167
 * @param {!Uint8Array} buffer The wav file buffer.
168
 * @param {!Object} signature The file signature.
169
 * @private
170
 */
171
function readCueChunk_(buffer, signature, wav, uInt32_) {
172
  /** @type {?Object} */
173
  let chunk = findChunk_(signature, 'cue ');
174
  if (chunk) {
175
    io.head_ = chunk.chunkData.start;
176
    wav.cue.chunkId = chunk.chunkId;
177
    wav.cue.chunkSize = chunk.chunkSize;
178
    wav.cue.dwCuePoints = io.read_(buffer, uInt32_);
179
    for (let i=0; i<wav.cue.dwCuePoints; i++) {
180
      wav.cue.points.push({
181
        dwName: io.read_(buffer, uInt32_),
182
        dwPosition: io.read_(buffer, uInt32_),
183
        fccChunk: io.readString_(buffer, 4),
184
        dwChunkStart: io.read_(buffer, uInt32_),
185
        dwBlockStart: io.read_(buffer, uInt32_),
186
        dwSampleOffset: io.read_(buffer, uInt32_),
187
      });
188
    }
189
  }
190
}
191
192
/**
193
 * Read the 'smpl' chunk of a wave file.
194
 * @param {!Uint8Array} buffer The wav file buffer.
195
 * @param {!Object} signature The file signature.
196
 * @private
197
 */
198
function readSmplChunk_(buffer, signature, wav, uInt32_) {
199
  /** @type {?Object} */
200
  let chunk = findChunk_(signature, 'smpl');
201
  if (chunk) {
202
    io.head_ = chunk.chunkData.start;
203
    wav.smpl.chunkId = chunk.chunkId;
204
    wav.smpl.chunkSize = chunk.chunkSize;
205
    wav.smpl.dwManufacturer = io.read_(buffer, uInt32_);
206
    wav.smpl.dwProduct = io.read_(buffer, uInt32_);
207
    wav.smpl.dwSamplePeriod = io.read_(buffer, uInt32_);
208
    wav.smpl.dwMIDIUnityNote = io.read_(buffer, uInt32_);
209
    wav.smpl.dwMIDIPitchFraction = io.read_(buffer, uInt32_);
210
    wav.smpl.dwSMPTEFormat = io.read_(buffer, uInt32_);
211
    wav.smpl.dwSMPTEOffset = io.read_(buffer, uInt32_);
212
    wav.smpl.dwNumSampleLoops = io.read_(buffer, uInt32_);
213
    wav.smpl.dwSamplerData = io.read_(buffer, uInt32_);
214
    for (let i=0; i<wav.smpl.dwNumSampleLoops; i++) {
215
      wav.smpl.loops.push({
216
        dwName: io.read_(buffer, uInt32_),
217
        dwType: io.read_(buffer, uInt32_),
218
        dwStart: io.read_(buffer, uInt32_),
219
        dwEnd: io.read_(buffer, uInt32_),
220
        dwFraction: io.read_(buffer, uInt32_),
221
        dwPlayCount: io.read_(buffer, uInt32_),
222
      });
223
    }
224
  }
225
}
226
227
/**
228
 * Read the 'data' chunk of a wave file.
229
 * @param {!Uint8Array} buffer The wav file buffer.
230
 * @param {!Object} signature The file signature.
231
 * @param {boolean} samples True if the samples should be loaded.
232
 * @throws {Error} If no 'data' chunk is found.
233
 * @private
234
 */
235
function readDataChunk_(buffer, signature, samples, wav) {
236
  /** @type {?Object} */
237
  let chunk = findChunk_(signature, 'data');
238
  if (chunk) {
239
    wav.data.chunkId = 'data';
240
    wav.data.chunkSize = chunk.chunkSize;
241
    if (samples) {
242
      wav.data.samples = buffer.slice(
243
        chunk.chunkData.start,
244
        chunk.chunkData.end);
245
    }
246
  } else {
247
    throw Error('Could not find the "data" chunk');
248
  }
249
}
250
251
/**
252
 * Read the 'bext' chunk of a wav file.
253
 * @param {!Uint8Array} buffer The wav file buffer.
254
 * @param {!Object} signature The file signature.
255
 * @private
256
 */
257
function readBextChunk_(buffer, signature, wav, uInt32_, uInt16_) {
258
  /** @type {?Object} */
259
  let chunk = findChunk_(signature, 'bext');
260
  if (chunk) {
261
    io.head_ = chunk.chunkData.start;
262
    wav.bext.chunkId = chunk.chunkId;
263
    wav.bext.chunkSize = chunk.chunkSize;
264
    wav.bext.description = io.readString_(buffer, 256);
265
    wav.bext.originator = io.readString_(buffer, 32);
266
    wav.bext.originatorReference = io.readString_(buffer, 32);
267
    wav.bext.originationDate = io.readString_(buffer, 10);
268
    wav.bext.originationTime = io.readString_(buffer, 8);
269
    wav.bext.timeReference = [
270
      io.read_(buffer, uInt32_),
271
      io.read_(buffer, uInt32_)];
272
    wav.bext.version = io.read_(buffer, uInt16_);
273
    wav.bext.UMID = io.readString_(buffer, 64);
274
    wav.bext.loudnessValue = io.read_(buffer, uInt16_);
275
    wav.bext.loudnessRange = io.read_(buffer, uInt16_);
276
    wav.bext.maxTruePeakLevel = io.read_(buffer, uInt16_);
277
    wav.bext.maxMomentaryLoudness = io.read_(buffer, uInt16_);
278
    wav.bext.maxShortTermLoudness = io.read_(buffer, uInt16_);
279
    wav.bext.reserved = io.readString_(buffer, 180);
280
    wav.bext.codingHistory = io.readString_(
281
      buffer, wav.bext.chunkSize - 602);
282
  }
283
}
284
285
/**
286
 * Read the 'ds64' chunk of a wave file.
287
 * @param {!Uint8Array} buffer The wav file buffer.
288
 * @param {!Object} signature The file signature.
289
 * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
290
 * @private
291
 */
292
function readDs64Chunk_(buffer, signature, wav, uInt32_) {
293
  /** @type {?Object} */
294
  let chunk = findChunk_(signature, 'ds64');
295
  if (chunk) {
296
    io.head_ = chunk.chunkData.start;
297
    wav.ds64.chunkId = chunk.chunkId;
298
    wav.ds64.chunkSize = chunk.chunkSize;
299
    wav.ds64.riffSizeHigh = io.read_(buffer, uInt32_);
300
    wav.ds64.riffSizeLow = io.read_(buffer, uInt32_);
301
    wav.ds64.dataSizeHigh = io.read_(buffer, uInt32_);
302
    wav.ds64.dataSizeLow = io.read_(buffer, uInt32_);
303
    wav.ds64.originationTime = io.read_(buffer, uInt32_);
304
    wav.ds64.sampleCountHigh = io.read_(buffer, uInt32_);
305
    wav.ds64.sampleCountLow = io.read_(buffer, uInt32_);
306
    //if (wav.ds64.chunkSize > 28) {
307
    //  wav.ds64.tableLength = unpack(
308
    //    chunkData.slice(28, 32), uInt32_);
309
    //  wav.ds64.table = chunkData.slice(
310
    //     32, 32 + wav.ds64.tableLength);
311
    //}
312
  } else {
313
    if (wav.container == 'RF64') {
314
      throw Error('Could not find the "ds64" chunk');
315
    }
316
  }
317
}
318
319
/**
320
 * Read the 'LIST' chunks of a wave file.
321
 * @param {!Uint8Array} buffer The wav file buffer.
322
 * @param {!Object} signature The file signature.
323
 * @private
324
 */
325
function readLISTChunk_(buffer, signature, wav, uInt32_, uInt16_) {
326
  /** @type {?Object} */
327
  let listChunks = findChunk_(signature, 'LIST', true);
328
  if (listChunks === null) {
329
    return;
330
  }
331
  for (let j=0; j < listChunks.length; j++) {
332
    /** @type {!Object} */
333
    let subChunk = listChunks[j];
334
    wav.LIST.push({
335
      chunkId: subChunk.chunkId,
336
      chunkSize: subChunk.chunkSize,
337
      format: subChunk.format,
338
      subChunks: []});
339
    for (let x=0; x<subChunk.subChunks.length; x++) {
340
      readLISTSubChunks_(subChunk.subChunks[x],
341
        subChunk.format, buffer, wav, uInt32_, uInt16_);
342
    }
343
  }
344
}
345
346
/**
347
 * Read the sub chunks of a 'LIST' chunk.
348
 * @param {!Object} subChunk The 'LIST' subchunks.
349
 * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
350
 * @param {!Uint8Array} buffer The wav file buffer.
351
 * @private
352
 */
353
function readLISTSubChunks_(subChunk, format, buffer, wav, uInt32_, uInt16_) {
354
  if (format == 'adtl') {
355
    if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
356
      io.head_ = subChunk.chunkData.start;
357
      /** @type {!Object<string, string|number>} */
358
      let item = {
359
        chunkId: subChunk.chunkId,
360
        chunkSize: subChunk.chunkSize,
361
        dwName: io.read_(buffer, uInt32_)
362
      };
363
      if (subChunk.chunkId == 'ltxt') {
364
        item.dwSampleLength = io.read_(buffer, uInt32_);
365
        item.dwPurposeID = io.read_(buffer, uInt32_);
366
        item.dwCountry = io.read_(buffer, uInt16_);
367
        item.dwLanguage = io.read_(buffer, uInt16_);
368
        item.dwDialect = io.read_(buffer, uInt16_);
369
        item.dwCodePage = io.read_(buffer, uInt16_);
370
      }
371
      item.value = io.readZSTR_(buffer, io.head_);
372
      wav.LIST[wav.LIST.length - 1].subChunks.push(item);
373
    }
374
  // RIFF INFO tags like ICRD, ISFT, ICMT
375
  } else if(format == 'INFO') {
376
    io.head_ = subChunk.chunkData.start;
377
    wav.LIST[wav.LIST.length - 1].subChunks.push({
378
      chunkId: subChunk.chunkId,
379
      chunkSize: subChunk.chunkSize,
380
      value: io.readZSTR_(buffer, io.head_)
381
    });
382
  }
383
}
384
385
/**
386
 * Read the 'junk' chunk of a wave file.
387
 * @param {!Uint8Array} buffer The wav file buffer.
388
 * @param {!Object} signature The file signature.
389
 * @private
390
 */
391
function readJunkChunk_(buffer, signature, wav) {
392
  /** @type {?Object} */
393
  let chunk = findChunk_(signature, 'junk');
394
  if (chunk) {
395
    wav.junk = {
396
      chunkId: chunk.chunkId,
397
      chunkSize: chunk.chunkSize,
398
      chunkData: [].slice.call(buffer.slice(
399
        chunk.chunkData.start,
400
        chunk.chunkData.end))
401
    };
402
  }
403
}
404